package my.jbf.firstGame.MODULE;
import android.graphics.Path;
import android.graphics.PointF;

import java.text.ParseException;




/*
 * Copyright (C) 2015 Jorge Castillo Pérez
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Entity to parse Svg paths to {@link Path} items understandable by the Android SDK. Obtained
 * from romainnurik Muzei implementation to avoid rewriting .
 *
 * @author romainnurik
 */
public class SvgPathParser
{
	private static final int TOKEN_ABSOLUTE_COMMAND = 1;
	private static final int TOKEN_RELATIVE_COMMAND = 2;
	private static final int TOKEN_VALUE = 3;
	private static final int TOKEN_EOF = 4;

	private int mCurrentToken;
	private PointF mCurrentPoint = new PointF();
	private int mLength;
	private int mIndex;
	private String mPathString;

	protected float transformX(float x)
	{
		return x;
	}

	protected float transformY(float y)
	{
		return y;
	}

	public Path parsePath(String s) throws ParseException
	{
		mCurrentPoint.set(Float.NaN, Float.NaN);
		mPathString = s;
		mIndex = 0;
		mLength = mPathString.length();

		PointF tempPoint1 = new PointF();
		PointF tempPoint2 = new PointF();
		PointF tempPoint3 = new PointF();

		Path p = new Path();
		p.setFillType(Path.FillType.WINDING);

		boolean firstMove = true;
		while (mIndex < mLength)
		{
			char command = consumeCommand();
			boolean relative = (mCurrentToken == TOKEN_RELATIVE_COMMAND);
			switch (command)
			{
				case 'M':
				case 'm': {
						// move command
						boolean firstPoint = true;
						while (advanceToNextToken() == TOKEN_VALUE)
						{
							consumeAndTransformPoint(tempPoint1, relative && mCurrentPoint.x != Float.NaN);
							if (firstPoint)
							{
								p.moveTo(tempPoint1.x, tempPoint1.y);
								firstPoint = false;
								if (firstMove)
								{
									mCurrentPoint.set(tempPoint1);
									firstMove = false;
								}
							}
							else
							{
								p.lineTo(tempPoint1.x, tempPoint1.y);
							}
						}
						mCurrentPoint.set(tempPoint1);
						break;
					}

				case 'C':
				case 'c': {
						// curve command
						if (mCurrentPoint.x == Float.NaN)
						{
							throw new ParseException("Relative commands require current point", mIndex);
						}

						while (advanceToNextToken() == TOKEN_VALUE)
						{
							consumeAndTransformPoint(tempPoint1, relative);
							consumeAndTransformPoint(tempPoint2, relative);
							consumeAndTransformPoint(tempPoint3, relative);
							p.cubicTo(tempPoint1.x, tempPoint1.y, tempPoint2.x, tempPoint2.y, tempPoint3.x,
									  tempPoint3.y);
						}
						mCurrentPoint.set(tempPoint3);
						break;
					}

				case 'L':
				case 'l': {
						// line command
						if (mCurrentPoint.x == Float.NaN)
						{
							throw new ParseException("Relative commands require current point", mIndex);
						}

						while (advanceToNextToken() == TOKEN_VALUE)
						{
							consumeAndTransformPoint(tempPoint1, relative);
							p.lineTo(tempPoint1.x, tempPoint1.y);
						}
						mCurrentPoint.set(tempPoint1);
						break;
					}

				case 'H':
				case 'h': {
						// horizontal line command
						if (mCurrentPoint.x == Float.NaN)
						{
							throw new ParseException("Relative commands require current point", mIndex);
						}

						while (advanceToNextToken() == TOKEN_VALUE)
						{
							float x = transformX(consumeValue());
							if (relative)
							{
								x += mCurrentPoint.x;
							}
							p.lineTo(x, mCurrentPoint.y);
						}
						mCurrentPoint.set(tempPoint1);
						break;
					}

				case 'V':
				case 'v': {
						// vertical line command
						if (mCurrentPoint.x == Float.NaN)
						{
							throw new ParseException("Relative commands require current point", mIndex);
						}

						while (advanceToNextToken() == TOKEN_VALUE)
						{
							float y = transformY(consumeValue());
							if (relative)
							{
								y += mCurrentPoint.y;
							}
							p.lineTo(mCurrentPoint.x, y);
						}
						mCurrentPoint.set(tempPoint1);
						break;
					}

				case 'Z':
				case 'z': {
						// close command
						p.close();
						break;
					}
			}
		}

		return p;
	}

	private int advanceToNextToken()
	{
		while (mIndex < mLength)
		{
			char c = mPathString.charAt(mIndex);
			if ('a' <= c && c <= 'z')
			{
				return (mCurrentToken = TOKEN_RELATIVE_COMMAND);
			}
			else if ('A' <= c && c <= 'Z')
			{
				return (mCurrentToken = TOKEN_ABSOLUTE_COMMAND);
			}
			else if (('0' <= c && c <= '9') || c == '.' || c == '-')
			{
				return (mCurrentToken = TOKEN_VALUE);
			}

			// skip unrecognized character
			++mIndex;
		}

		return (mCurrentToken = TOKEN_EOF);
	}

	private char consumeCommand() throws ParseException
	{
		advanceToNextToken();
		if (mCurrentToken != TOKEN_RELATIVE_COMMAND && mCurrentToken != TOKEN_ABSOLUTE_COMMAND)
		{
			throw new ParseException("Expected command", mIndex);
		}

		return mPathString.charAt(mIndex++);
	}

	private void consumeAndTransformPoint(PointF out, boolean relative) throws ParseException
	{
		out.x = transformX(consumeValue());
		out.y = transformY(consumeValue());
		if (relative)
		{
			out.x += mCurrentPoint.x;
			out.y += mCurrentPoint.y;
		}
	}

	private float consumeValue() throws ParseException
	{
		advanceToNextToken();
		if (mCurrentToken != TOKEN_VALUE)
		{
			throw new ParseException("Expected value", mIndex);
		}

		boolean start = true;
		boolean seenDot = false;
		int index = mIndex;
		while (index < mLength)
		{
			char c = mPathString.charAt(index);
			if (!('0' <= c && c <= '9') && (c != '.' || seenDot) && (c != '-' || !start))
			{
				// end of value
				break;
			}
			if (c == '.')
			{
				seenDot = true;
			}
			start = false;
			++index;
		}

		if (index == mIndex)
		{
			throw new ParseException("Expected value", mIndex);
		}

		String str = mPathString.substring(mIndex, index);
		try
		{
			float value = Float.parseFloat(str);
			mIndex = index;
			return value;
		}
		catch (NumberFormatException e)
		{
			throw new ParseException("Invalid float value '" + str + "'.", mIndex);
		}
	}
}
